package io.donlon.android.remotecontrol;

import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.UiThread;

import java.io.*;
import java.net.Socket;

public class Connection extends Handler {
    private static Connection connection;

    private CommandReceiver cmdReceiver;
    private ConnectionClosedListener connectionClosedListener;

    private final Socket socket;
    private final OutputStream outputStream;
    private final InputStream inputStream;

    private Handler senderThreadHandler;
    private Looper senderThreadLooper;

    private boolean isWaitingResponse = false; // TODO: use condition variable
    private final byte[] waitingResponseKeyLock = new byte[0];
    private String waitingResponseKey;

    private final Thread senderThread = new Thread("Connection Message Sender") {
        // public Handler mHandler;

        public void run() {
            Looper.prepare();
            Looper looper = Looper.myLooper();
            if (looper == null) {
                return;
            }
            senderThreadLooper = looper;
            senderThreadHandler = new Handler(looper);
            Looper.loop();
        }
    };

    private final Thread receiverThread = new Thread("Connection Message Receiver") {
        public void run() {
            Handler mainThreadHandler = new Handler(Looper.getMainLooper());
            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
            while (true) {
                try {
                    String s = reader.readLine();
                    if (s == null) {
                        break;
                    }
                    if (cmdReceiver == null || s.isEmpty()) {
                        continue;
                    }
                    String[] cmds = s.split(";");
                    synchronized (waitingResponseKeyLock) {
                        for (String cmd : cmds) {
                            if (!TextUtils.isEmpty(cmd) && waitingResponseKey != null) {
                                if (cmd.startsWith(waitingResponseKey)) {
                                    waitingResponseKey = null;
                                    isWaitingResponse = false;
                                }
                            }
                        }
                    }
                    mainThreadHandler.post(() -> {
                        for (String cmd : cmds) {
                            if (!TextUtils.isEmpty(cmd)) {
                                cmdReceiver.onCommandReceived(cmd);
                            }
                        }
                    });
                } catch (IOException e) {
                    e.printStackTrace();
                    break;
                }
            }
            if (connectionClosedListener != null) {
                mainThreadHandler.post(() -> connectionClosedListener.onConnectionClosed());
            }
        }
    };

    private Connection(String host, int port) throws IOException {
        socket = new Socket(host, port);
        // socket.setSoTimeout(1000);
        outputStream = socket.getOutputStream();
        inputStream = socket.getInputStream();
        senderThread.start();
        receiverThread.start();
    }

    @Override
    public void handleMessage(@NonNull Message msg) {
    }

    public void sendCommand(String data) {
        try {
            outputStream.write(data.getBytes());
            outputStream.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void close() {
        try {
            socket.close();
            // senderThread.interrupt();
            senderThreadLooper.quit();
            receiverThread.interrupt();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void setCommandReceiver(CommandReceiver receiver) {
        cmdReceiver = receiver;
    }

    public void setConnectionClosedListener(ConnectionClosedListener listener) {
        connectionClosedListener = listener;
    }

    public static boolean isConnected() {
        return connection != null && connection.socket.isConnected();
    }

    public static Connection getConnection() {
        return connection;
    }

    public static boolean connect(String host, int port) {
        Connection newConnection;
        try {
            newConnection = new Connection(host, port);
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
        if (connection != null) {
            connection.close();
        }
        connection = newConnection;
        return true;
    }

    public static void postCommand(String command, String waitResponse) {
        if (!isConnected()) {
            return;
        }
        if (TextUtils.isEmpty(command)) {
            return;
        }
        long start = System.currentTimeMillis();
        connection.senderThreadHandler.post(() -> {
            while (connection.isWaitingResponse) {
                try {
                    Thread.sleep(10);
                    long now = System.currentTimeMillis();
                    if (now - start > 1000) {
                        connection.waitingResponseKey = null;
                        connection.isWaitingResponse = false;
                        return;
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            Log.d("RC", command);
            if (!TextUtils.isEmpty(waitResponse)) {
                synchronized (connection.waitingResponseKeyLock) {
                    connection.isWaitingResponse = true;
                    connection.waitingResponseKey = waitResponse;
                }
            }
            connection.sendCommand(command);
        });
    }

    public static void postCommand(String command) {
        postCommand(command, null);
    }

    public interface CommandReceiver {
        void onCommandReceived(@NonNull String cmd);
    }

    public interface ConnectionClosedListener {
        @UiThread
        void onConnectionClosed();
    }
}
